#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

#define __GLOBAL__

#include "consts.h"
#include "common.h"
#include "syscall.h"
#include "disks.h"
#include "sys_struct.h"
#include "function.h"

int panic(const char* message)
{
    printf("%s\n", message);
    exit(1);
}

int get_system_time()
{
    return 0;
}
/*
 * allocate a file_handle for an inode. return the file handle.
 */
static int open_inode(int inode, int amode)
{
    int inode_index = load_inode(inode);
    int fd;

    if ( (fd=allocate_handle()) == -1 )
        return -1;
    
    file_handles[fd].inode_no = inode_index;
    file_handles[fd].access = amode;
    file_handles[fd].rw_offset = 0;
    inode_buffers[inode_index].count ++;

    return fd;
}
/*
 * get the item index by the filename.
 */
static int find_entry(const file_entry* table, const char* filename, int len)
{
	int i;
	for (i=0; i<len; i++)
		if ( strcmp(table[i].filename , filename)==0 )
			return i;
	return -1;
}

/**
 *split the path.
 *For a path like "AAA/BBB/CCC", the parent will be AAA. and the return value will be 4.
 * */
static int split_this_dir(const char* path, char* parent)
{
	int i;
	for ( i=0; path[i] && path[i]!='/' && i<28; i++ ) parent[i]=path[i];

	if ( i>=28 ) return -1;
	parent[i]=0;
	if ( path[i]=='/' ) i++;
	return i;
}

/**
 *Parse a path. The fd is the parent dir handle. All the return value will be stored in pi.
 *If file name is too long, the pi->fd_p will be the handle of parent dir,
 *the pi->ino_c will be -1, and the pi
 * */
static void path_parse(int _fd, const char* _filename, path_info *pi)
{
	static file_entry inode_table[128];
	static char name[30];

	int len = split_this_dir(_filename, name);

	if ( len == -1 )
	{
		fill_path_info(pi, ERR_FILENAME_LONG, _fd, -1, _filename);
		return;
	}

	int bytes_get, result=-1;

	while ( (bytes_get = readfile(_fd, &inode_table, 4096) ) > 0 )
	{
		result = find_entry( inode_table, name, bytes_get / sizeof(file_entry) );
		if ( result != -1 )
			break;
	}

	if ( result != -1 )
	{
		if ( _filename[len] != 0 ){
			closefile(_fd);
			path_parse( open_inode( inode_table[result].i_no, I_READ|I_WRITE), _filename + len, pi );
		}
		else fill_path_info(pi, 3, _fd, inode_table[result].i_no, NULL);
	}
	else
	{
		fill_path_info(pi,(_filename[len]==0?ERR_FILE_ABSENT:ERR_DIR_ABSENT), _fd, -1, _filename);
	}
}

static int get_inode(const char* _filename, path_info *pi)
{
	int fd;
    if ( _filename[0]=='/'){
        fd = open_inode(0, I_READ | I_WRITE);
        _filename++;
    }
    else {
        fd = open_inode( current_dir.ino, I_READ | I_WRITE);
    }

    if ( fd==-1) return -1;
    path_parse(fd, _filename, pi);
    return 1;
}

static void getfilename(const char* fullpathname, char* store)
{
	int len = strlen(fullpathname);
	while ( fullpathname[--len] != '/' );
	strcpy(store, fullpathname + len + 1);
}

static int check_mode(int inode_mode, int fd_mode)
{
    if ( READABLE(fd_mode) && !IS_READ(inode_mode) ) return 0;
    if ( WRITABLE(fd_mode) && !IS_WRITE(inode_mode) ) return 0;
    return 1;
}

/** 
 * Create a file by name. If a file of the same name exists, erase it to empty. Or else a new file is created.
 * Creating operation on a existing but readonly file will fail.
 * imode: read, write or both.
 * */
int createfile(const char* filename, int imode)
{
	static path_info pi;
	static file_entry new_entry;
    int i, fd;
    inode_memory* ibuffer;
    
	if ( get_inode(filename, &pi) == -1 ) return -1;

    fd = allocate_handle();
    if ( fd==-1 ) return -1;
    
    if ( pi.status == ERR_FILENAME_LONG || pi.status == ERR_DIR_ABSENT )
        return -1;
    else if ( pi.status == 3 )
    {
        i = load_inode(pi.ino_c);
        if ( i==-1 ) return -1;
        ibuffer = &inode_buffers[i];
        if ( !WRITABLE(imode) || check_mode(ibuffer->i_mode, imode)==0 ) return -1;
        free_data( ibuffer );
    }

    else
    {
        i = create_inode();
        if ( i== -1 ) return -1;
        ibuffer = &inode_buffers[i];
        ibuffer->count = 0;

        new_entry.i_no = ibuffer->i_no_disk;
        strcpy(new_entry.filename, pi.p);
        writefile(pi.fd_p, &new_entry, sizeof(new_entry));
    }

    ibuffer->count++;
    if ( READABLE(imode) ) ibuffer->i_mode &= M_READ;
    if ( WRITABLE(imode) ) ibuffer->i_mode &= M_WRITE;
    ibuffer->i_modify_time = get_system_time();
    ibuffer->size = 0;

    file_handles[fd].access = imode;
    file_handles[fd].inode_no = i;
    file_handles[fd].rw_offset = 0;
    closefile(pi.fd_p);
    
    return fd;
}

/*
 *Open a file by name. If same file exist, do the priviledge check.
 *Or fail and return -1.
 *imode: read, write or both.
 * */
int openfile(const char* filename, int imode)
{
    static path_info pi;
    int fd, i;
    inode_memory* ibuffer;
    
    get_inode(filename, &pi);
    closefile(pi.fd_p);
    
    if (pi.status!=3) return -1;
    fd = allocate_handle();
    if ( fd==-1 ) return -1;

    i = load_inode( pi.ino_c );
    if ( i== -1 ) return -1;

    ibuffer = &inode_buffers[i];
    if ( check_mode(ibuffer->i_mode, imode)==0 ) return -1;

    ibuffer->count ++;
    file_handles[fd].access = imode;
    file_handles[fd].inode_no = i;
    file_handles[fd].rw_offset = 0;

    return fd;
}

int readfile(int _fd, void* b, int len)
{
    int phy_no, offset, getcnt=0, size;
    byte* p=(byte*)b;
    
    inode_memory* curinode = &inode_buffers[file_handles[_fd].inode_no];
    while ( file_handles[_fd].rw_offset < curinode->size && len>0 )
    {
        phy_no = get_phy_no( curinode, file_handles[_fd].rw_offset/BLOCK_SIZE );
        offset = file_handles[_fd].rw_offset % BLOCK_SIZE;

        size = MIN(BLOCK_SIZE-offset,curinode->size-file_handles[_fd].rw_offset);
        size = MIN(size,len);
        readwrite(p, phy_no, offset, size, RW_READ);
        p+=size;
        file_handles[_fd].rw_offset+=size;
        len -= size;
        getcnt += size;
    }

    return getcnt;
}

int writefile(int _fd, void* buffer, int len)
{
    int writecnt = 0, phy_no, offset, size;

    file_handle* pfd = &file_handles[_fd];
    inode_memory *curinode = &inode_buffers[ pfd->inode_no ];
    byte* p = (byte*)buffer;
    //this should not be happen.
    if ( pfd->rw_offset > curinode->size ) return -1;
    
    while ( len>0 )
    {
        if ( pfd->rw_offset < curinode->size || curinode->size%BLOCK_SIZE != 0 )
        {
            phy_no = get_phy_no(curinode, pfd->rw_offset / BLOCK_SIZE );
            offset = pfd->rw_offset % BLOCK_SIZE;
            size = MIN(BLOCK_SIZE-offset,len);
        }
        else {
            //fail because exceed the max file size
            if ( curinode->size > MAX_FILE_SIZE )
                return -1;
            //fail because no more disk block.
            if ( (phy_no=allocate_data(curinode))==-1 )
                return -1;
            size = MIN(len, BLOCK_SIZE);
            offset = 0;
        }

        readwrite(p, phy_no, offset, size, RW_WRITE);
        pfd->rw_offset += size;
        if ( curinode->size < pfd->rw_offset )
            curinode->size = pfd->rw_offset;
        writecnt += size;
        p+=size;
        len -= size;
    }

    return writecnt;
}

int seekfile(int _fd, int offset, int mode)
{
    int off;
    int size = inode_buffers[file_handles[_fd].inode_no].size;
    
    switch ( mode )
    {
	case SEEK_BEGIN: off = 0; break;
	case SEEK_CURRENT: off = file_handles[_fd].rw_offset; break;
	case SEEK_LAST: off = size; break;
    }

    if ( off + offset < 0 || off + offset > size )
        return -1;
    
    file_handles[_fd].rw_offset = off + offset;
    
    return 0;
}

int closefile(int _fd)
{
    if ( file_handles[_fd].inode_no == -1 ) return 1;
    
    inode_buffers[ file_handles[_fd].inode_no ].count -- ;
    file_handles[_fd].inode_no = -1;
    file_handles[_fd].rw_offset = 0;
    
    return 1;
}
